package com.alcatel_lucent.coff.service

import com.alcatel_lucent.coff.model.COFFFile
import com.alcatel_lucent.coff.model.sections.*
import org.apache.commons.io.IOUtils
import org.apache.commons.lang3.ArrayUtils

import java.nio.ByteBuffer
import java.nio.ByteOrder

import static org.apache.commons.lang3.ArrayUtils.indexOf
import static org.apache.commons.lang3.ArrayUtils.subarray

/**
 * Created with IntelliJ IDEA.
 * User: Administrator
 * Date: 13-4-13
 * Time: 下午3:07
 * To change this template use File | Settings | File Templates.
 */
class COFFParser {

    private static COFFParser parser = new COFFParser()

    public static COFFParser getInstance() {
        return parser
    }

    public static COFFFile parse(File file) {
        if (null == file || !file.exists()) return null
        ByteBuffer memMap = ByteBuffer.wrap(IOUtils.toByteArray(file.newInputStream()))
        memMap.order(ByteOrder.nativeOrder())
        COFFFile coffFile = new COFFFile(fileHeader: readFileHeader(memMap))
        byte[] tmpBuf = new byte[coffFile.fileHeader.usOptHdrSZ]
        memMap.get(tmpBuf)

        if (coffFile.fileHeader.usOptHdrSZ > 0) coffFile.optHeader = readOptHeader(ByteBuffer.wrap(tmpBuf))
        readSections(coffFile, memMap)

        coffFile.symbols = []
        //read SymbolTable
        int endSymbolOffset = coffFile.fileHeader.ulSymbolOffset + coffFile.fileHeader.ulNumSymbol * SymEnt.LEN
        for (int offset = coffFile.fileHeader.ulSymbolOffset; offset < endSymbolOffset; offset += SymEnt.LEN) {
            ByteBuffer symbolMem = ByteBuffer.wrap getBufferFromBuffer(memMap, offset, SymEnt.LEN)
            symbolMem.order(ByteOrder.nativeOrder())

            tmpBuf = new byte[8]
            symbolMem.get(tmpBuf)

            int endSignIndex = indexOf(tmpBuf, 0 as byte)
            if (-1 != endSignIndex) {
                tmpBuf = subarray(tmpBuf, 0, endSignIndex)
            }

            symbolMem.position(0)

            coffFile.symbols.add(
                    new SymEnt(
                            cName: new String(tmpBuf),
                            ulZero: symbolMem.int,
                            ulOffset: symbolMem.int,
                            ulValue: symbolMem.int,
                            iSection: symbolMem.short,
                            usType: symbolMem.short,
                            usClass: symbolMem.get(),
                            usNumAux: symbolMem.get()
                    )
            )
        }

        coffFile.stringTable = [:]
        memMap.position(endSymbolOffset)
        int tabLen = memMap.int
        int offset = 4

        tmpBuf = new byte[tabLen - offset]
        memMap.get(tmpBuf)

        new String(tmpBuf).split('\0').each { str ->
            coffFile.stringTable.put(offset, str)
            offset += str.length() + 1
        }

        //fix symbol names
        coffFile.symbols.each { symbol->
            if (0==symbol.ulZero)symbol.cName = coffFile.stringTable[symbol.ulOffset]
        }

        //fix relocation table
        coffFile.reLocTable.values() .each {reLocCollection->
            reLocCollection.each {reLoc->
                reLoc.symbol = coffFile.symbols[reLoc.ulSymbol]
            }
        }

        coffFile
    }

    private static void readSections(COFFFile coffFile, ByteBuffer memMap) {
        //read sections
        coffFile.sectionHeaders = []
        coffFile.sectionData = [:]
        coffFile.reLocTable = [:] as Map<String, Collection<ReLoc> >
        coffFile.LineNumberTable = [:]


        SectionHeader sectionHeader
        for (int i = 0; i < coffFile.fileHeader.usNumSec; ++i) {
            byte[] buf = new byte[8]
            memMap.get(buf)
            int endSignIndex = indexOf(buf, 0 as byte)
            if (-1 != endSignIndex) {
                buf = subarray(buf, 0, endSignIndex)
            }
            sectionHeader = new SectionHeader(
                    cName: new String(buf),

                    ulVSize: memMap.int,
                    ulVaddr: memMap.int,
                    ulSize: memMap.int,
                    ulSecOffset: memMap.int,
                    ulRelOffset: memMap.int,
                    ulLNOffset: memMap.int,

                    usNumRel: memMap.short,
                    usNumLN: memMap.short,

                    ulFlags: memMap.int
            )
            coffFile.sectionHeaders.add(sectionHeader)

            //read section related data like section data, relocation table, line number table etc.

            //read section data
            coffFile.sectionData[sectionHeader.cName] = 0 != sectionHeader.ulSecOffset ?
                getBufferFromBuffer(memMap, sectionHeader.ulSecOffset, sectionHeader.ulSize) : ArrayUtils.EMPTY_BYTE_ARRAY

            //read relocation tab
            int endRelOffset = sectionHeader.ulRelOffset + sectionHeader.usNumRel * ReLoc.LEN
            Collection<ReLoc> reLocs = []
            for (int offset = sectionHeader.ulRelOffset; offset < endRelOffset; offset += ReLoc.LEN) {
                ByteBuffer reLocMem = ByteBuffer.wrap getBufferFromBuffer(memMap, offset, ReLoc.LEN)
                reLocMem.order(ByteOrder.nativeOrder())
                reLocs.add(new ReLoc(
                        ulAddr: reLocMem.int,
                        ulSymbol: reLocMem.int,
                        usType: reLocMem.short
                ))
            }
            coffFile.reLocTable[sectionHeader.cName] = reLocs

            //read LineNumber table
            int endLineNumberOffset = sectionHeader.ulLNOffset + sectionHeader.usNumLN * LineNumber.LEN
            Collection<LineNumber> lineNumbers = []
            for (int offset = sectionHeader.ulLNOffset; offset < endLineNumberOffset; offset += LineNumber.LEN) {
                ByteBuffer lineNumberMem = ByteBuffer.wrap getBufferFromBuffer(memMap, offset, LineNumber.LEN)
                lineNumberMem.order(ByteOrder.nativeOrder())
                lineNumbers.add(new LineNumber(
                        ulAddrORSymbol: lineNumberMem.int,
                        usLineNo: lineNumberMem.short
                ))
            }
            coffFile.LineNumberTable[sectionHeader.cName] = lineNumbers
        }
    }

    static byte[] getBufferFromBuffer(ByteBuffer buffer, int offset, int len) {
        ByteBuffer tmpBuf = ByteBuffer.allocate(len)
        int end = offset + len
        while (offset < end) {
            tmpBuf.put(buffer.get(offset++))
        }
        return tmpBuf.array()
    }

    private static OptHeader readOptHeader(ByteBuffer memMap) {
        short magicNum = memMap.short
        if (0x4c01 != magicNum) throw new Exception(String.format("Unknown common object file format(magicNum=%04X).", magicNum))
        OptHeader optHeader = new OptHeader(
                usMagic: magicNum,
                usVersion: memMap.short,

                ulTextSize: memMap.int,
                ulInitDataSZ: memMap.int,
                ulUninitDataSZ: memMap.int,
                ulEntry: memMap.int,
                ulTextBase: memMap.int
        )

        if (memMap.position() < memMap.limit()) {
            optHeader.ulDataBase = memMap.int
        }

        optHeader
    }

    private static FileHeader readFileHeader(ByteBuffer memMap) {
        return new FileHeader(
                usMagic: memMap.short,
                usNumSec: memMap.short,
                ulTime: new Date(memMap.int * 1000L),

                ulSymbolOffset: memMap.int,
                ulNumSymbol: memMap.int,

                usOptHdrSZ: memMap.short,
                usFlags: memMap.short
        )
    }

    public static String getCodeSegmentDisAssemblerCmd(Collection<SectionHeader> sectionHeaders, String fileName = 'targetFile', String codeHeaderName = '.text') {
        String cmd = ''
        if (null == sectionHeaders || sectionHeaders.isEmpty()) return cmd
        sectionHeaders.each { sh ->
            if (sh.cName.trim().equals(codeHeaderName)) {
                cmd = "ndisasm -u -e${sh.ulSecOffset} -k${sh.ulSize},10000 ${fileName}"
            }
        }

        cmd
    }
}
